﻿using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using TTUnityPlugin;
using TTGame;

namespace TTGameEngine {

public enum ResSuffixStr {
    prefab,
    ogg,
    wav,
    mp3,
    xml,
    txt,
    custom, //自定义后缀名，不额外附加
}

/// <summary>
/// 1、只在Load的时候加载。默认会根据主城、战斗设置分类。
/// 2、加载UI相关，手动设置分类。
/// 3、释放UI时，只有当仅存在UI标志时才会释放。
/// </summary>
public enum ResCategory {
    Nothing = 0,
    Global = 1,
    City = 2,
    Battle = 4,
    UI = 8,
}

public delegate void Func_LoadResOK(string name, Object resObj);

public class WaitLoadRes {
    public string key;
    public string fullpath;
    public Func_LoadResOK func;
    public ResCategory resCate;
    public bool debug_haslog;
    public AssetBundleLoader loader;

    public void Init(string aKey, string aFullpath, AssetBundleLoader aLoader, Func_LoadResOK aFunc, ResCategory aResCate, bool aDebug_hasLog) {
        key = aKey;
        fullpath = aFullpath;
        loader = aLoader;
        func = aFunc;
        resCate = aResCate;
        debug_haslog = aDebug_hasLog;
    }

    public void Init() {
        key = "";
        fullpath = "";
        loader = null;
        func = null;
        resCate = ResCategory.Nothing;
        debug_haslog = false;
    }

    public string DumpStr() {
        return string.Format("key[{0} path[{1}] loaderComplete[{2}]", key, fullpath, loader.isComplete.ToString());
    }
}

[System.Serializable]
public class ResCache {
    public string name;
    public Object resObj;
    int categoryMask = 0;

    //标记是否为常驻资源
    public bool isConst {
        get {
            return ((categoryMask & (int)ResCategory.Global) > 0);
        }
    }

    public void AddCategroy(ResCategory aResCate) {
        AddCategroy((int)aResCate);
    }

    public void AddCategroy(int aResCate) {
        categoryMask |= (int)aResCate;
    }

    public void ClearCategroy() {
        categoryMask = 0;
    }

    public bool IsCategroy(ResCategory aResCate) {
        return IsCategroy((int)aResCate);
    }

    public bool IsCategroy(int aResCate) {
        return (categoryMask & (int)aResCate) > 0;
    }

    public void Init(string aName, ResCategory aResCate, Object aResObj) {
        Init(aName, (int)aResCate, aResObj);
    }
    public void Init(string aName, int aResCate, Object aResObj) {
        name = aName;
        resObj = aResObj;
        categoryMask = aResCate;
    }

    public void Init() {
        name = "";
        resObj = null;
        categoryMask = 0;
    }

}
/*
 * 游戏资源加载与缓存
 *
 * 缓存加载的Prefab。对于GameObject由使用者处理是否缓存，比如效果对象的ObjRecyle机制。
 *
 * You Should Know
 * 1、传入的资源路径前缀不需要加Asset/Resources/。回调时，使用的是传入时的路径为唯一标识。
 *      原因1、即使改成异步加载，也是有小部分可能依旧使用原来的加载。还有一些插件会使用原来的加载。路径如果重新设定标准的话，变动很多，不好。
 *
 *
 * mark:youhua 清理ResLoader和GameObjectLoader之间功能重复的代码。
 */
public class ObjectLoader : MonoBehaviour {
    //key是Unity格式的资源路径（不带Asset/Resources，不带后缀名），如"prefabs/luoli"
    protected Dictionary<string, ResCache> cachedPrefabs = new Dictionary<string, ResCache>();
    static string RES_ROOT_PATH = "Assets/Resources/";
    static string RES_EXT_ROOT_PATH = "Assets/ResExternal/";
    //static string RES_ROOT_PATH = "Assets/ResExternal/Resources/";
    //mark:debug 是否使用异步加载资源
    public static bool Debug_isDynLoadRes = true;
    //刻意的延迟加载回调时间。模拟加载延时。
    public static bool Debug_isDelayLoadRes = false;
    public static float Debug_DelayLoadResTime = 3.0f;

#if UNITY_EDITOR
    public List<ResCache> caches = new List<ResCache>();
#endif

    PoolCacher<WaitLoadRes> waitLoadResPool;
    PoolCacher<ResCache> resCachePool;
    LinkedList<WaitLoadRes> waitLoadReses = new LinkedList<WaitLoadRes>();

    //当前场景的资源分类，用于释放资源的策略
    static public ResCategory curSceneCategory = ResCategory.Nothing;

    void initWaitLoadResForPool(WaitLoadRes resblock, bool isStoredObj) {
        resblock.Init();
    }

    void initResCacheForPool(ResCache cache, bool isStoredObj) {
        cache.Init();
    }

    WaitLoadRes ObtainWaitLoadRes() {
        if(waitLoadResPool == null) {
            waitLoadResPool = new PoolCacher<WaitLoadRes>(initWaitLoadResForPool);
        }
        return waitLoadResPool.ObtainObj();
    }

    ResCache ObtainResCache() {
        if(resCachePool == null) {
            resCachePool = new PoolCacher<ResCache>(initResCacheForPool);
        }
        return resCachePool.ObtainObj();
    }

    #region Load Public Method
    public bool IsPrefabExist(string path) {
        if(string.IsNullOrEmpty(path)) {
            return false;
        }

        ResCache res;
        if(cachedPrefabs.TryGetValue(path, out res)) {
            if(res.resObj == null) {
                return false;
            } else {
                return true;
            }
        }
        return false;
    }

    /// <summary>
    /// 注意：调用前请确保已预加载。 预加载调用PreloadPrefab。
    /// </summary>
    /// <returns>The prefab object.</returns>
    /// <param name="prefabPath">Prefab path.</param>
    /// <param name="isReLoad"> 如果没有预加载，是否使用Resources.Load重新加载 </param>
    public Object GetPrefabObj(string prefabPath, bool isReLoad = true) {
        if(prefabPath == "") {
            DebugUtil.LogError("[objLoader] GetPrefabObj. PrefabPath is empty");
            return null;
        }
        Object obj = _getPrefabObj(prefabPath);
        if(obj == null && isReLoad) {
            obj = debug_loadLocal(prefabPath, null);
            //DebugUtil.LogError("[GameObjMgr] Need Preload. Path:" + prefabPath);
        }
        return obj;
    }

    //清理所有缓存
    public void ClearAllResCache() {
        List<ResCache> cacheList = new List<ResCache>(cachedPrefabs.Values);
        cachedPrefabs.Clear();
        for(int i = 0, count = cacheList.Count; i < count; i++) {
            ResCache cache = cacheList[i];
            if(cache.IsCategroy(ResCategory.Global) == false) {
                if(cache.resObj != null && (cache.resObj is Texture || cache.resObj is AudioClip)) {
                    Resources.UnloadAsset(cache.resObj);
                }
                cache.Init();
                resCachePool.BackToCache(cache);
            } else {
                cachedPrefabs.Add(cache.name, cache);
            }
        }

        Debug_refreshShowCaches();
    }

    void Debug_refreshShowCaches() {
#if UNITY_EDITOR
        caches.Clear();
        caches.AddRange(cachedPrefabs.Values);
#endif
    }

    //清除内存 只在进入战斗或城市时才清理内存
    public void ClearMemery(bool isCollectInfo, ResCategory sceneCategory) {
        List<string> removeList = new List<string>();
        int globalConstRes = 0;
        int sceneConstRes = 0;
        int removeAndKeep = 0;
        for(var ite = cachedPrefabs.GetEnumerator(); ite.MoveNext();) {
            var pair = ite.Current;
            ResCache cache = pair.Value;
            //常用资源 跳过
            if(cache.isConst) {
                globalConstRes++;
                continue;
            }
            //if(cache.sceneMask ==0 || cache.sceneMask ==3)
            if(cache.IsCategroy(ResCategory.Battle | ResCategory.City)) {
                //在战斗和city中用过的预设不卸载
                sceneConstRes++;
                continue;
            }
            //清理不是当前场景的资源
            if(cache.IsCategroy(sceneCategory) == false) {
                if(cache.resObj is Texture || cache.resObj is AudioClip) {
                    Resources.UnloadAsset(cache.resObj);

                }
                cache.resObj = null;
                //第一次只清理obj，不清理数据结构，占个位置。
                if(isCollectInfo == false) {
                    removeList.Add(pair.Key);
                } else {
                    removeAndKeep++;
                }
            }
        }

        for(int i = 0; i < removeList.Count; i++) {
            string resPath = removeList[i];
            ResCache res = null;
            if(cachedPrefabs.TryGetValue(resPath, out res)) {
                res.Init();
                resCachePool.BackToCache(res);
                cachedPrefabs.Remove(resPath);
            }
        }

        Debug_refreshShowCaches();
        DebugUtil.LogFormat("[ObjectLoader] 已清理掉 {0} 个资源！ 剩余[{1}]个资源，其中全局资源[{2}] 场景公用资源[{3}] 空占位资源[{4}]", removeList.Count + removeAndKeep, cachedPrefabs.Count, globalConstRes, sceneConstRes, removeAndKeep);

    }

    /// <summary>
    /// 预加载资源。如果已有资源，会直接调用回调。
    ///
    /// 注：即使加载出现错误也会触发回调，不过参数obj为null。
    /// </summary>
    /// <param name="path">传入的路径格式统一为Unity格式，如"prefabs/luoli"</param>
    /// <param name="func"></param>
    /// <param name="isGlobalRes">是否是全局资源。全局资源不会因切关而释放</param>
    /// <param name="debug_hasLog">是否输出log，默认true。如果该资源允许没有，则先填false防止Log太多。</param>
    /// <returns> 是否加载失败。加载失败也会触发回调。 </returns>
    public bool PreloadPrefab(string path, Func_LoadResOK func, ResSuffixStr suffix = ResSuffixStr.prefab, ResCategory resCate = ResCategory.Nothing, bool debug_hasLog = false) {
        if(string.IsNullOrEmpty(path)) {
            if(func != null) {
                func(path, null);
            }
            return false;
        }
        Object obj = _getPrefabObj(path, resCate);
        if(obj != null) {
            if(func != null) {
                func(path, obj);
            }
        } else {
            string key = path;
            string fullPath = completePath(path, suffix);
            loadPrefab(key, fullPath, func, resCate, debug_hasLog);
        }
        return false;
    }

    /// <summary>
    /// 每一个资源加载成功都会触发一次回调
    /// </summary>
    /// <param name="pathList"></param>
    /// <param name="func"></param>
    public void PreloadPrefabList(List<string> pathList, Func_LoadResOK func, ResSuffixStr suffix = ResSuffixStr.prefab, ResCategory resCate = ResCategory.Nothing, bool debug_hasLog = false) {
        if(Debug_isDynLoadRes == false) {
            return;
        }

        if(pathList.Count <= 0) {
            return;
        }
        loadPrefabList(pathList, func, suffix, resCate, debug_hasLog);
    }

    /// <summary>
    ///
    /// 参数resCategory：如果对象仅仅是该Category则删除。
    /// 某些对象可能是SceneCate+UI，SceneCate是指场景的缓存。UICate是指临时打开某个UI的缓存
    /// resCategory = UI，指删除仅仅是UI临时缓存的对象。
    /// </summary>
    /// <param name="key">Key.</param>
    /// <param name="resCategory">Res category.</param>
    public void UnloadCachedPrefab(string key, ResCategory resCategory = ResCategory.Nothing) {
        ResCache cache = null;
        if(resCategory == ResCategory.UI) {
            if(cachedPrefabs.TryGetValue(key, out cache)) {
                //删除UIs判断：非全局，非当前场景场景，则可删除。
                if(cache.IsCategroy(ResCategory.Global) == false && cache.IsCategroy(curSceneCategory) == false) {
                    cachedPrefabs.Remove(key);
                }
            }
        } else {
            cachedPrefabs.Remove(key);
        }

        Debug_refreshShowCaches();
    }

    public void ClearCache() {
        cachedPrefabs.Clear();
        Debug_refreshShowCaches();
    }
    #endregion
    //=======================================================

    #region Load Private Method
    void loaderCompleted(AssetBundleLoader loader, string key, string fullPath, ResCategory resCate, Func_LoadResOK func, bool debug_hasLog = false) {
        string error = string.Empty;
        Object resObj = loader.LoadAsset(fullPath);

        if(debug_hasLog) {
            DebugUtil.LogFormat("[ObjLoader] Load End[{0}]", fullPath);
        }
        if(resObj != null) {
            cachePrefab(key, resObj, resCate);
            if(func != null) {
                func(key, resObj);
            }
        } else {
            if(func != null) {
                DebugUtil.LogError("[ResLoad] Res Load Failed. Path:[" + fullPath + "]\n Func[" + func.Target.ToString() + "." + func.Method.ToString() + "]\n ErrorStr:" + error);
                func(key, null);
            } else {
                DebugUtil.LogError("[ResLoad] Res Load Failed. Path:[" + fullPath + "]\n ErrorStr:" + error);
            }
        }
#if Debug_LoadItem
        DebugUtil.LogFormat("[ObjLoader] load obj OK. [{0}]  path:[{1}]", key, fullPath);
#endif
    }

    bool checkResLoadOK(WaitLoadRes res) {
        var loader = res.loader;
        if(loader.isComplete == false) {
            return false;
        }

        bool isLoadOK = false;
        loaderCompleted(loader, res.key, res.fullpath, res.resCate, res.func, res.debug_haslog);
        return true;
    }

    void Update() {
        if(waitLoadReses.Count <= 0) {
            return;
        }

        for(var node = waitLoadReses.First; node != null;) {
            WaitLoadRes res = node.Value;
            if(res != null && checkResLoadOK(res)) {
                //DebugUtil.LogFormat("[ObjLoader] End load res:[{0}]", res.key);
                res.Init();
                waitLoadResPool.BackToCache(res);
                var nextNode = node.Next;
                waitLoadReses.Remove(node);
                node = nextNode;
            } else {
                node = node.Next;
            }
        }
    }

    public void DumpInfo() {
        string strWaitResesInfo = "";

        foreach(WaitLoadRes res in waitLoadReses) {
            strWaitResesInfo += (res.DumpStr() + "\n");
        }

        DebugUtil.LogFormat("[ObjectLoader]\n等待加载完成的对象：\n {0}", strWaitResesInfo);
    }

    //    IEnumerator C_LoadPrefabWork() {
    //        isLoadingPrefab = true;
    //        while(waitLoadReses.Count > 0) {
    //            for(var node = waitLoadReses.First; node != null;) {
    //                WaitLoadRes res = node.Value;
    //                if(res != null && checkResLoadOK(res)) {
    //                    //DebugUtil.LogFormat("[ObjLoader] End load res:[{0}]", res.key);
    //                    res.Init();
    //                    waitLoadResPool.BackToCache(res);
    //                    var nextNode = node.Next;
    //                    waitLoadReses.Remove(node);
    //                    node = nextNode;
    //                } else {
    //                    node = node.Next;
    //                }
    //            }
    //            yield return null;
    //        }
    //
    //        isLoadingPrefab = false;
    //    }

    /// <summary>
    ///
    /// </summary>
    /// <param name="key">唯一标识。缓存与回调时用到。</param>
    /// <param name="fullPath">资源加载路径。以Asset/开头的绝对路径</param>
    /// <param name="func"> 回调(string name,Object resObj)</param>
    /// <param name="isGlobalRes"> 全局资源将不会在切关时被清理。 </param>
    /// <param name="debug_hasLog"> </param>
    void addPollingLoadingRes(string key, string fullPath, AssetBundleLoader loader, Func_LoadResOK func, ResCategory resCate, bool debug_hasLog = false) {
        WaitLoadRes res = ObtainWaitLoadRes();
        res.Init(key, fullPath, loader, func, resCate, debug_hasLog);
        waitLoadReses.AddLast(res);
#if Debug_LoadItem
        DebugUtil.LogFormat("[ObjLoader] add load obj:[{0}]  path:[{1}]", key, fullPath);
#endif
        //        if(isLoadingPrefab == false) {
        //            StartCoroutine(C_LoadPrefabWork());
        //        }
    }

    //mark:edit 减少协程创建，将资源加载的协程合并成一个。
    void loadPrefab(string key, string fullPath, Func_LoadResOK func, ResCategory resCate, bool debug_hasLog = false) {
#if UNITY_EDITOR
        if(fullPath[fullPath.Length - 1] == ' ') {
            DebugUtil.LogError("[ObjLoader] Prefab Path is Error. Has a extra space.（路径后面有个空格，混蛋） name:'" + fullPath + "'");
        }
#endif
        if(debug_hasLog) {
            DebugUtil.LogFormat("[ObjLoader] load res:[{0}]", fullPath);
        }

        AssetBundleLoader loader = SingletonMgr.GetInst<AssetBundleManager>().LoadRes(fullPath);
        if(loader.isComplete) {
            loaderCompleted(loader, key, fullPath, resCate, func, debug_hasLog);
        } else {
            addPollingLoadingRes(key, fullPath, loader, func, resCate, debug_hasLog);
        }
    }

    void loadPrefabList(List<string> aPathList, Func_LoadResOK func, ResSuffixStr suffix, ResCategory resCate = ResCategory.Nothing, bool debug_hasLog = true) {
        int Count = aPathList.Count;
        if(Count <= 0)
            return;

        List<string> pathList = new List<string>(aPathList);
        for(int i = 0; i < pathList.Count; i++) {
            string path = pathList[i];
            string key = path;
            string fullPath = completePath(path, suffix);

            if(debug_hasLog) {
                DebugUtil.LogFormat("[ObjLoader] C_LoadPrefabList LoadStart. fullpath[{0}]", fullPath);
            }

#if UNITY_EDITOR
            if(path[path.Length - 1] == ' ') {
                DebugUtil.LogError("[ObjLoader] PrefabName is Error. Has a extra space. name:'" + fullPath + "'");
            }
#endif

            if(debug_hasLog) {
                DebugUtil.LogFormat("[ObjLoader] C_LoadPrefabList loadPrefab...");
            }

            //var loader = ResLoader.Instance.Load(fullPath);
            var loader = SingletonMgr.GetInst<AssetBundleManager>().LoadRes(fullPath);
            if(loader.isComplete) {
                loaderCompleted(loader, key, fullPath, resCate, func, debug_hasLog);
            }
            addPollingLoadingRes(key, fullPath, loader, func, resCate, debug_hasLog);

        }

        if(debug_hasLog) {
            DebugUtil.LogFormat("[ObjLoader] C_LoadPrefabList Func End");
        }
    }

    Object _getPrefabObj(string prefabPath, ResCategory aResCate = ResCategory.Nothing) {
        //Object resObj = null;
        ResCache cache;
        if(Debug_isDynLoadRes == false) {
            return debug_loadLocal(prefabPath, null);
        } else {
            if(cachedPrefabs.TryGetValue(prefabPath, out cache)) {
                if(aResCate == ResCategory.Nothing) {
                    //do nothing
                } else if(aResCate == ResCategory.UI) {
                    cache.AddCategroy(aResCate);
                } else {
                    cache.AddCategroy((int)aResCate | (int)curSceneCategory);
                }
                return cache.resObj;
            }
            return null;
        }
    }

    protected void cachePrefab(string key, Object prefab, ResCategory aResCate) {
        if(prefab == null) {
            return;
        }

        ResCache res;
        int sceneCate = 0;
        if(aResCate == ResCategory.Nothing) {
            sceneCate = 0;
        } else if(aResCate == ResCategory.UI) {
            sceneCate = (int)aResCate;
        } else {
            sceneCate = (int)aResCate | (int)curSceneCategory;
        }

        if(cachedPrefabs.TryGetValue(key, out res)) {
            res.resObj = prefab;
            res.AddCategroy(sceneCate);
        } else {
            //在未进入游戏前cache的预设 始终不卸载
            ResCache newOne = ObtainResCache();
            newOne.Init(key, sceneCate, prefab);
            cachedPrefabs.Add(key, newOne);
        }

        Debug_refreshShowCaches();
    }

    public void Debug_mergeCache(string from, string to) {
        if(cachedPrefabs.ContainsKey(from)) {
            cachedPrefabs.TryAdd(to, cachedPrefabs[from]);
        }

        Debug_refreshShowCaches();
    }

    /// <summary>
    /// 检查路径，完善路径
    ///
    /// 最终绝对路径是，以Assets/开头,并带有后缀名的。        ///
    /// </summary>
    /// <param name="path"></param>
    /// <param name="suffix"></param>
    /// <returns></returns>
    string completePath(string noSuffixPath, ResSuffixStr suffix) {
        string path;
        if(noSuffixPath.StartsWith(RES_ROOT_PATH) || noSuffixPath.StartsWith(RES_EXT_ROOT_PATH)) {
            path = noSuffixPath;
        } else {
            path = RES_ROOT_PATH + noSuffixPath;
        }

        if(suffix != ResSuffixStr.custom) {
            path = path + "." + suffix.ToString();
        }
        return path;
    }

    //将绝对路径转为相对路径。用于调试时恢复Res.load的加载。
    string debug_getLoadPath(string path) {
        if(path.StartsWith(RES_ROOT_PATH)) {
            path = path.Substring(RES_ROOT_PATH.Length);
        }
        return path;
    }

    Object debug_loadLocal(string path, Func_LoadResOK func) {
        path = debug_getLoadPath(path);

        Object obj = Resources.Load(path);
        cachePrefab(path, obj, ResCategory.Nothing);
        if(func != null)
            func(path, obj);
        return obj;
    }
    #endregion

}
}